<div id="menu-editor-wrapper"></div>

<script crossorigin src="//unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="//unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="//unpkg.com/react-dnd@2.6.0/dist/ReactDnD.min.js"></script>
<script src="//unpkg.com/react-dnd-html5-backend@2.6.0/dist/ReactDnDHTML5Backend.min.js"></script>
<script>
{% verbatim %}
'use strict';

const e = React.createElement;


class Modal extends React.Component {
    render(){
        return e('div', { 'id': this.props.id, 'class': 'modal fade', 'tabindex': -1, role: 'dialog'},
            e('div', { 'class': 'modal-dialog' + (this.props.large ? ' modal-lg' : ''), role: 'document' },
                e('div', { 'class': 'modal-content' }, this.props.children)
            )
        );
    }
}


class MenuEditor extends React.Component {
    render(){
        var {menu, sections, addSection, unflattenSitemap, moveLink,
            addEditLink, deleteLink, saveLink} = this.props;

        return e(Modal, { open: false, id: 'menu-editor-modal' },
            e('div', { 'class': 'modal-header' }, [
                e('h5', { 'class': 'modal-title' }, 'Edit navigation'),
                e('button', { 'type': 'button', 'class': 'close', 'data-dismiss': 'modal', 'aria-label': 'Close' },
                    e('span', { 'aria-hidden': 'true' }, '×')
                ),
            ]),
            e('div', { 'class': 'modal-body' }, [
                menu ? e('div', {class:'menu-editor-links'}, sections.map((sectionId) => (
                    e(Link, { sectionId, moveLink, parent: -1, sitemap: menu, edit: addEditLink, deleteLink, save: saveLink })
                ))) : null,
                e('div', { 'class': 'text-right' }, [
                    e('button', { 'class': 'btn btn-outline-secondary', 'type': 'button', onClick: addSection }, '+ Add section'),
                    e('button', { 'class': 'btn btn-outline-primary', 'type': 'button', onClick: addEditLink, 'data-toggle': 'modal', 'data-target': '#menu-link-editor-modal' }, '+ Add link')
                ])
            ]),
            e('div', { 'class': 'modal-footer' }, [
                e('button', { 'class': 'btn btn-secondary', 'type': 'button', 'data-dismiss': 'modal' }, 'Cancel'),
                e('button', { 'class': 'btn btn-primary', onClick: unflattenSitemap, 'data-dismiss': 'modal' }, 'Save')
            ])
        );
    }
}


class LinkEditor extends React.Component {
    componentDidUpdate(prevProps, prevState){
        if (this.props.linkToAddEdit !== prevProps.linkToAddEdit){
            var {enTitle, enURL, zhTitle, zhURL} = this.refs;

            if (this.props.linkToAddEdit){
                var link = this.props.menu[this.props.linkToAddEdit];

                enTitle.value = link.title.en;

                if (enURL)
                    enURL.value = link.link.en;

                zhTitle.value = link.title.zh;
                if (zhURL)
                    zhURL.value = link.link.zh;

            } else {
                // Remove all fields.
                enTitle.value = zhTitle.value = '';

                if (enURL)
                    enURL.value = zhURL.value = '';
            }
        }
    }

    saveLink(){
        var {enTitle, enURL, zhTitle, zhURL} = this.refs;

        var newLinkProps = {
            title: {
                en: enTitle.value,
                zh: zhTitle.value
            }
        }
        if (this.props.linkToAddEdit === null || this.props.menu[this.props.linkToAddEdit].parent !== -1)
            newLinkProps.link = {
                en: enURL.value,
                zh: zhURL.value
            };

        this.props.saveLink(this.props.linkToAddEdit, newLinkProps);
    }

    render(){
        var languageFields = [
            { id: 'en', language: 'English', titleLabel: 'Link title', urlLabel: 'Paste path to file'},
            { id: 'zh', language: '中文', titleLabel: 'Link title', urlLabel: 'Paste path to file'}
        ];

        return e(Modal, { /*open: this.props.linkToAddEdit !== undefined,*/ id: 'menu-link-editor-modal', large: true }, [
            e('div', { 'class': 'modal-header' }, [
                e('h5', { 'class': 'modal-title' }, 'Add or edit link'),
                e('button', { 'type': 'button', 'class': 'close', 'data-dismiss': 'modal', 'aria-label': 'Close' },
                    e('span', { 'aria-hidden': 'true', onClick: this.props.closeAddEditLink }, '×')
                ),
            ]),
            e('div', { 'class': 'modal-body container' }, languageFields.map((languageField) => {
                return e('div', { 'class': 'row mb-3' }, [
                    e('div', {'class': 'col'}, languageField.language),
                    e('div', {'class': 'col-5'},
                        e('input', {'type': 'text', 'class': 'form-control', placeholder: languageField.titleLabel, ref: languageField.id + 'Title' })
                    ),
                    this.props.linkToAddEdit === null || (this.props.linkToAddEdit &&
                        this.props.menu[this.props.linkToAddEdit].parent !== -1) ? e('div', {'class': 'col-5'},
                        e('input', {'type': 'text', 'class': 'form-control', placeholder: languageField.urlLabel, ref: languageField.id + 'URL' })
                    ) : null
                ]);
            })),
            e('div', { 'class': 'modal-footer' }, [
                e('button', { 'class': 'btn btn-secondary', 'type': 'button', 'data-dismiss': 'modal', onClick: this.props.closeAddEditLink }, 'Cancel'),
                e('button', { 'class': 'btn btn-primary', onClick: this.saveLink.bind(this), 'data-dismiss': 'modal' }, 'Save')
            ])
        ]);
    }
}


var LinkSource = {
  beginDrag: function (props) {
    return {
      sectionId: props.sectionId,
      sitemap: props.sitemap
    };
  }
}


const LinkTarget = {
    canDrop(){
        return false;
    },
	hover(props, monitor, component) {
		const drag = props.sitemap[monitor.getItem().sectionId],
            hover = props.sitemap[props.sectionId];

        if (props.sectionId === drag.parent)
            return;

        const dragIndex = drag.position
		const hoverIndex = hover.position

		// Don't replace items with themselves
		if (monitor.getItem().sectionId === props.sectionId) {
			return
		}

		// Determine rectangle on screen
		const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect()

		// Get vertical middle
		const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2

		// Determine mouse position
		const clientOffset = monitor.getClientOffset()

		// Get pixels to the top
		const hoverClientY = clientOffset.y - hoverBoundingRect.top

		// Only perform the move when the mouse has crossed half of the items height
		// When dragging downwards, only move when the cursor is below 50%
		// When dragging upwards, only move when the cursor is above 50%

		// Dragging downwards
		if (drag.parent === hover.parent && (dragIndex < hoverIndex && hoverClientY < hoverMiddleY)) {
			return
		}

		// Dragging upwards
		if (drag.parent === hover.parent && (dragIndex > hoverIndex && hoverClientY > hoverMiddleY)) {
			return
		}

		// Time to actually perform the action
		props.moveLink(monitor.getItem().sectionId, drag, props.sectionId, hover)

		// Note: we're mutating the monitor item here!
		// Generally it's better to avoid mutations,
		// but it's good here for the sake of performance
		// to avoid expensive index searches.
		monitor.getItem().sectionId = props.sectionId
	},
};


function collect(connect, monitor) {
  return {
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging(),
  };
}


class Link extends React.Component {
    edit(event){
        var {edit, sectionId, sitemap} = this.props;
        edit(sectionId);
    }

    delete(event){
        var {deleteLink, sectionId, sitemap} = this.props,
            link = sitemap[sectionId];

        if (window.confirm('Are you sure you want to delete the link: "' + (
            link.title.en + ' / ' + link.title.zh + '"?')))
            deleteLink(sectionId);
    }

    render(){
        const {
			text,
			isDragging,
			connectDragSource,
			connectDropTarget,
            highlighted,
            hovered,

            sectionId,
            moveLink,
            deleteLink,
            sitemap,
            edit
		} = this.props;

        var section = sitemap[sectionId];

        return connectDragSource && connectDropTarget && connectDragSource(connectDropTarget(e('div', { 'class': 'menu-editor-link' + (section.parent === -1 ? ' container' : '') },
            e('div', { 'class': 'menu-editor-link-info row justify-content-between', style: (hovered ? { backgroundColor: '#efefef', opacity: (isDragging ? 0.5 : 1) } : null) },
                e('div', { 'class': 'menu-editor-link-info-title col-8' }, section.title.en + ' / ' + section.title.zh),
                e('div', { 'class': 'menu-editor-link-info-actions col' }, [
                    e('span', { 'class': 'menu-editor-link-info-action', onClick: this.edit.bind(this), 'data-toggle': 'modal', 'data-target': '#menu-link-editor-modal' }, e('i', { 'class': 'fas fa-edit' })),
                    e('span', { 'class': 'menu-editor-link-info-action', onClick: this.delete.bind(this) }, e('i', { 'class': 'fas fa-trash' }))
                ])
            ),
            e(LinkItems, { moveLink, sectionId, sitemap, edit, deleteLink })
        )))
    }
}


class LinkItems extends React.Component {
    render(){
        const { moveLink, parent, sectionId, sitemap, edit, deleteLink } = this.props;

        // Get unique sections.
        const sections = Object.keys(sitemap).filter(
            (sId) => sitemap[sId].parent === sectionId).sort((a, b) => {
                return sitemap[a].position - sitemap[b].position;
            });

        return sections ? e('div', null, sections.map((si) => (
            e(Link, { sectionId: si, moveLink, parent, sitemap, edit, deleteLink })
        ))) : null;
    }
}


class MenuEditorWrapper extends React.Component {
    constructor(props){
        super(props);

        this.flattenSitemap = this.flattenSitemap.bind(this);
        this.unflattenSitemap = this.unflattenSitemap.bind(this);

        this.state = { open: false };

        this.moveLink = this.moveLink.bind(this);
        this.getSectionIds = this.getSectionIds.bind(this);
        this.getLinksIdsForParent = this.getLinksIdsForParent.bind(this);
        this.getSortedSectionIds = this.getSortedSectionIds.bind(this);
        this.positionSorter = this.positionSorter.bind(this);

        this.addSection = this.addSection.bind(this);
        this.addEditLink = this.addEditLink.bind(this);
        this.closeAddEditLink = this.closeAddEditLink.bind(this);
        this.deleteLink = this.deleteLink.bind(this);
        this.saveLink = this.saveLink.bind(this);
    }

    componentWillMount(){
        var view = this;
        $.get('/get-menu', function(response, xhr){
            view.setState({ sitemap: view.flattenSitemap(response) });
        });
    }

    componentDidMount(){
        var view = this;
        $('.sidebar-edit').click((event) => {
            view.setState({ open: true });
        });
    }

    uuid(){
        /*jshint bitwise:false */
        var i, random;
        var uuid = '';

        for (i = 0; i < 32; i++) {
            random = Math.random() * 16 | 0;
            if (i === 8 || i === 12 || i === 16 || i === 20) {
                uuid += '-';
            }
            uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
        }

        return uuid;
    }

    flattenSitemap(sitemap, flatSitemap={}, parent=-1){
        var view = this, sectionId;

        if (!sitemap || !sitemap.sections)
            return;

        sitemap.sections.forEach((section, position) => {
            sectionId = view.uuid();
            flatSitemap[sectionId] = {
                title: section.title, link: section.link,
                parent, position
            }

            view.flattenSitemap(section, flatSitemap, sectionId);
        });

        return flatSitemap;
    }

    serializeSections(sectionIds){
        var subsections, view = this, {sitemap} = this.state, section;

        return sectionIds.map((sectionId) => {
            section = sitemap[sectionId];

            return Object.assign(section.parent !== -1 ? { link: section.link } : {}, {
                title: section.title,
                sections: view.serializeSections(
                    this.getLinksIdsForParent(sectionId))
            });
        });
    }

    unflattenSitemap(event){
        const { sitemap } = this.state;
        let unflattenedSitemap = {
            sections: this.serializeSections(this.getSortedSectionIds())
        };
        event.preventDefault();

        $.post('/save-menu', { menu: JSON.stringify(unflattenedSitemap) }, () => {
            window.location.reload();
        });
    }

    moveLink(dragSectionId, drag, hoverSectionId, hover){
        if (drag.parent === hover.parent){
            window.replaced = true;
            var dragPosition = drag.position;

            this.setState({ sitemap: Object.assign({}, this.state.sitemap, {
                [dragSectionId]: Object.assign(drag, { position: hover.position }),
                [hoverSectionId]: Object.assign(hover, { position: dragPosition })
            }) });
        }
    }

    getSectionIds(){
        return this.getLinksIdsForParent();
    }

    getLinksIdsForParent(parent=-1){
        var {sitemap} = this.state;

        return sitemap ? Object.keys(sitemap).filter(
            (sId) => sitemap[sId].parent === parent) : null;
    }

    getSortedSectionIds(parent=-1){
        var {sitemap} = this.state,
            sectionIds = this.getLinksIdsForParent(parent);

        // Get unique sections.
        return sectionIds ? sectionIds.sort(this.positionSorter): null;
    }

    positionSorter(a, b){
        var {sitemap} = this.state;
        return sitemap[a].position - sitemap[b].position;
    }

    addSection(eventOrId){
        var {sitemap} = this.state,
            defaultName;

        if (typeof(eventOrId) === 'string')
            defaultName = this.state.sitemap[eventOrId].title;

        var sectionName = prompt('Name your new section (in English)',
            defaultName);

        if (sectionName){
            var sortedSectionIds = this.getSortedSectionIds();

            this.setState({ sitemap: Object.assign(this.state.sitemap, { [this.uuid()]: {
                title: { en: sectionName, zh: null },
                parent: -1,
                position: sortedSectionIds ? sitemap[sortedSectionIds[sortedSectionIds.length - 1]].position + 1 : 0
            } }) });
        }
    }

    addEditLink(eventOrId){
        var linkId = typeof(eventOrId) === 'string' ? eventOrId : null;

        this.setState({ linkToAddEdit: linkId });
    }

    closeAddEditLink(){
        this.setState({ linkToAddEdit: undefined });
    }

    deleteLink(linkId){
        var {sitemap} = this.state,
            link = sitemap[linkId],
            siblings = this.getLinksIdsForParent(link.parent),

            // Push back positions of items after this.
            siblingsAfterThisIds = siblings.filter(function(sibling){
                return sitemap[sibling].position > sitemap[linkId].position;
            }),

            siblingAfterThis;

        delete sitemap[linkId];

        siblingsAfterThisIds.forEach((siblingAfterThisId) => {
            siblingAfterThis = sitemap[siblingAfterThisId];
            sitemap = Object.assign(sitemap, { [siblingAfterThisId]: Object.assign(
                siblingAfterThis, { position: siblingAfterThis.position - 1 }) });
        });

        this.setState({ sitemap });
    }

    saveLink(linkId, newProps){
        var {sitemap} = this.state;

        if (linkId)
            this.setState({ sitemap: Object.assign(sitemap, {
                [linkId]: Object.assign(sitemap[linkId], newProps)
            })});
        else {
            var sortedSectionIds = this.getSortedSectionIds(),
                lastSectionId = sortedSectionIds[sortedSectionIds.length - 1],
                lastLinkIdOfLastSection = this.getSortedSectionIds(lastSectionId);

            this.setState({ sitemap: Object.assign(sitemap, {
                [this.uuid()]: {...newProps, parent: lastSectionId,
                    position: lastLinkIdOfLastSection ? sitemap[lastLinkIdOfLastSection].position + 1 : 0
                }
            }) });
        }

        this.setState({ linkToAddEdit: undefined });
    }

	render() {
        const {addSection, unflattenSitemap, moveLink,
            addEditLink, closeAddEditLink, deleteLink, saveLink} = this,
            { sitemap, linkToAddEdit, open } = this.state,
            sections = this.getSortedSectionIds();

        var menu = sitemap;

		return (
			e('div', {id: 'menu-editor'}, [
                e(MenuEditor, {
                    menu, sections,
                    addSection, unflattenSitemap, moveLink, addEditLink,
                    deleteLink
                }),
                e(LinkEditor, { linkToAddEdit, menu, closeAddEditLink, saveLink }),
			])
		);
	}
}


MenuEditorWrapper = ReactDnD.DragDropContext(ReactDnDHTML5Backend)(MenuEditorWrapper);

Link = ReactDnD.DropTarget('LINK', LinkTarget, (connect, monitor) => ({
	connectDropTarget: connect.dropTarget(),
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver({ shallow: false })
}))(Link);
Link = ReactDnD.DragSource('LINK', LinkSource, collect)(Link);

window.addEventListener('load', ()=>{
    ReactDOM.render(e(MenuEditorWrapper), document.getElementById('menu-editor-wrapper'));
})
{% endverbatim %}
</script>
